package top.rainbowecho.upload.application.command.impl;

import com.google.common.base.Stopwatch;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.AsyncRabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import top.rainbowecho.common.domain.shared.vo.SensitiveRecognizeResult;
import top.rainbowecho.common.mq.config.BlogMqRouteEnum;
import top.rainbowecho.common.mq.config.MqConfiguration;
import top.rainbowecho.common.util.CommonUtil;
import top.rainbowecho.upload.application.command.BlogFsService;
import top.rainbowecho.upload.config.FsProperties;
import top.rainbowecho.upload.domain.model.fs.strategy.ImageUploadStrategy;
import top.rainbowecho.upload.domain.model.fs.vo.UploadResult;
import top.rainbowecho.upload.infrastructure.db.data.UploadRecord;
import top.rainbowecho.upload.infrastructure.db.repository.MongoUploadRepository;

import java.io.IOException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @author rainbow
 * @since 2019/12/6 21:32
 */
@Service
@Slf4j
public class BlogFsServiceImpl implements BlogFsService {

    private MongoUploadRepository mongoUploadRepository;

    private FsProperties fsProperties;

    private AsyncRabbitTemplate asyncRabbitTemplate;

    private MqConfiguration mqConfiguration;

    @Autowired
    public void setMqConfiguration(MqConfiguration mqConfiguration) {
        this.mqConfiguration = mqConfiguration;
    }

    @Autowired
    public void setAsyncRabbitTemplate(AsyncRabbitTemplate asyncRabbitTemplate) {
        this.asyncRabbitTemplate = asyncRabbitTemplate;
    }

    @Autowired
    public void setFsProperties(FsProperties fsProperties) {
        this.fsProperties = fsProperties;
    }

    @Autowired
    public void setMongoUploadRepository(MongoUploadRepository mongoUploadRepository) {
        this.mongoUploadRepository = mongoUploadRepository;
    }

    /**
     * 上传文件到fs
     *
     * @param file 上传的文件
     * @param username 上传的用户名
     * @param uploadStrategy 文件异步上传策略，选择进行普通图片上传还是缩略图上传
     * @return 如果图片属于敏感内容，直接返回敏感标签；如果不属于，则返回文件的浏览器访问的uri
     * @throws IOException 文件读取失败
     */
    @Override
    public UploadResult uploadFile(MultipartFile file, String username, ImageUploadStrategy uploadStrategy) throws IOException, InterruptedException, ExecutionException, TimeoutException {
        // 1. 使用MQ发送消息到AI service, 进行图片审核
        String imageBase64Code = CommonUtil.parseBase64(file.getInputStream());
        Stopwatch aiRecognizeImage = Stopwatch.createStarted();
        AsyncRabbitTemplate.RabbitConverterFuture<Object> rabbitFuture = asyncRabbitTemplate
                .convertSendAndReceive(mqConfiguration.getRoutes().get(BlogMqRouteEnum.FILE_TO_AI).getRouting(), imageBase64Code);
        long aiRecognizeDuration = aiRecognizeImage.elapsed(TimeUnit.MILLISECONDS);
        log.info("图片AI审核用时：" + aiRecognizeDuration);

        // 2. 此部分是lambda表达式，选择进行图片上传或生成缩略图，如果是直接图片上传，返回值是文件的可访问路径；如果是缩略图上传，返回值是缩略图的可访问路径
        String filename = file.getOriginalFilename();
        String extension = extractFileExtension(Objects.requireNonNull(filename));

        Stopwatch uploadStarted = Stopwatch.createStarted();
        Future<String> uploadFuture = uploadStrategy.upload(file.getInputStream(), extension);
        long uploadDuration = uploadStarted.elapsed(TimeUnit.MILLISECONDS);
        log.info("异步上传文件用时：" + uploadDuration);

        // 3. 根据审核结果和上传结果，插入文件上传记录到MongoDB
        SensitiveRecognizeResult recognizeResult = (SensitiveRecognizeResult) rabbitFuture.get(3, TimeUnit.SECONDS);
        int sensitive = recognizeResult.isSensitive() ? 1 : 0;
        String storePathWithGroup = uploadFuture.get(1, TimeUnit.SECONDS);
        UploadRecord record = new UploadRecord(storePathWithGroup, username, filename, LocalDateTime.now(), sensitive, recognizeResult.getLabel());

        Stopwatch recordInsertStarted = Stopwatch.createStarted();
        // 如果label为空，则说明消费者端调用接口产生了错误，不进行记录的插入，如果不为空直接插入记录，且不需要获取返回值
        if (!StringUtils.isEmpty(recognizeResult.getLabel())) {
            mongoUploadRepository.insert(record);
        }
        long insertDuration = recordInsertStarted.elapsed(TimeUnit.MILLISECONDS);
        log.info("插入上传记录到MongoDB用时：" + insertDuration);

        String pathWithToken = this.generateToken(storePathWithGroup);
        String data = recognizeResult.isSensitive() ? recognizeResult.getLabel() : pathWithToken;

        return new UploadResult(recognizeResult.isSensitive(), data);
    }

    /**
     * 根据文件名获取文件拓展名
     *
     * @param filename 文件名 ***.jpg格式
     * @return 文件拓展名
     */
    private String extractFileExtension(String filename) {
        String[] split = filename.split("\\.");
        return split[split.length - 1];
    }

    /**
     * 根据文件存放的全路径重新生成token
     *
     * @param storePath 文件的全路径
     * @return 新的携带token和ts参数的uri路径
     */
    @Override
    public String generateToken(String storePath) {
        if (fsProperties.isCheck()) {
            // 生成token
            int timestamp = (int) Instant.now().getEpochSecond();
            int index = StringUtils.indexOf(storePath, "/");
            String path = storePath.substring(index + 1);

            String token = CommonUtil.generateToken(path, fsProperties.getSecretKey(), timestamp);

            // 生成带token的uri
            return storePath + "?token=" + token + "&ts=" + timestamp;
        } else {
            return storePath;
        }
    }
}
